#include <mach/mach_types.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <sys/systm.h>
#include <sys/kauth.h>
#include <sys/vnode.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/kern_control.h>
#include <sys/ioctl.h>
#include <sys/sys_domain.h>

#include <libkern/OSMalloc.h>

#include "FireWallList.h"

#define stringSize 255
#define buffSize 1024
#define add 2
#define del 3

kern_return_t Drv_start(kmod_info_t * ki, void *d);
kern_return_t Drv_stop(kmod_info_t *ki, void *d);

struct list *l;

/* LOG PART */

int getLogLength();
int moveOffset();
vfs_context_t logvfs;
static int offset = 0;
static int isWriteToLogFirst = 1;
static char previousChar = '\0';

void writeToLog(char * text);
void write(char text[]);

struct dateTime dateAndTimeFromSeconds();

struct dateTime
{
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
};

/* SOCKET PART */

char g_string_buf[buffSize]={0};
static kern_ctl_ref g_ctl_ref;
static boolean_t g_filter_registered = FALSE;
static int hello_ctl_set(kern_ctl_ref ctl_ref, u_int32_t unit, void* unitinfo, int opt, void* data, size_t len);
static int hello_ctl_connect(kern_ctl_ref ctl_ref, struct sockaddr_ctl *sac, void** unitinfo);
static errno_t hello_ctl_disconnect(kern_ctl_ref ctl_ref, u_int32_t unit, void* unitinfo);
static struct kern_ctl_reg g_kern_ctl_reg = {
    "com.osxkernel.HelloKernControl",
    0,
    0,
    CTL_FLAG_PRIVILEGED,
    0,
    0,
    hello_ctl_connect,
    hello_ctl_disconnect,
    NULL,
    hello_ctl_set,
    NULL
};

/* LISTEN-BLOCK ;  READ-WRITE MAGIC KEY; */

void checkMagicKey(char *path,vnode_t vn,vfs_context_t vfs,uint8_t magicKey[]); // get MKey from exec vnode

static kauth_listener_t processListener;

static int callbackProcessListener  // process Listener/Blocker
(
 kauth_cred_t    credential,
 void *          idata,
 kauth_action_t  action,
 uintptr_t       arg0,
 uintptr_t       arg1,
 uintptr_t       arg2,
 uintptr_t       arg3
 );

void getExPath(char path[]){
    char ppa[3] = "ppa";
    printf("\n\nApp\n");
    char app[3]={0};
    
    for (int i = 0; i< 3;  i++) {
        app[i]=path[strlen(path)-(i+1)];
    }
   
    for (int i = 0; i< 3;  i++) {
        if (app[i]==ppa[i]) {
            continue;
        }
    }

    int nameL = 20;
    int p = strlen(path);
    
    for(int i = p; i>0;i--)
        if(path[i]=='/'){
            p=++i;
            break;
        }

    char ex[20] = {0};
    for(int i = 0; i<nameL;i++){
        if(path[p]=='.') break;
        ex[i] = path[p];
        p++;
    }
     strncat(path, "/Contents/MacOS/", strlen("/Contents/MacOS/"));
     strncat(path, ex, strlen(ex));
       
}

kern_return_t Drv_start(kmod_info_t * ki, void *d)
{
    l = new_list();
    logvfs = vfs_context_current();
    if(getLogLength() == 1)
    {
        offset = 0;
    }

    int ret = ctl_register(&g_kern_ctl_reg, &g_ctl_ref);
    if (ret == KERN_SUCCESS)
    {
        
        processListener = kauth_listen_scope(KAUTH_SCOPE_VNODE, callbackProcessListener,NULL);
        
        printf("Start\n");
        writeToLog("FireWall been started");
        g_filter_registered = TRUE;
        
        return KERN_SUCCESS;
    }
    
    else return KERN_FAILURE;
    
}

kern_return_t Drv_stop(kmod_info_t *ki, void *d)

{   
   kauth_unlisten_scope(processListener);

   processListener = NULL;
    
   delete_list(l);
    
    if (g_filter_registered)
        ctl_deregister(g_ctl_ref);
    
    printf("Stop\n");
    writeToLog("FireWall been stopped");
    return KERN_SUCCESS;
}


/* SOCKET PART */

static int hello_ctl_connect(kern_ctl_ref ctl_ref, struct sockaddr_ctl *sac, void** unitinfo)
{
    return 0;
}

static errno_t hello_ctl_disconnect(kern_ctl_ref ctl_ref, u_int32_t unit, void* unitinfo)
{
    return 0;
}

static int hello_ctl_set(kern_ctl_ref ctl_ref, u_int32_t unit, void* unitinfo, int opt, void* data, size_t len)
{
    int ret = 0;
    for (int i=0;i<stringSize;i++)
      g_string_buf[i]='\0';
    
    switch (opt) {
            
        case add:
            strncpy(g_string_buf, (char*)data, min(stringSize, len));
            printf("Kext get a path: %s\n", g_string_buf);
            writeToLog("ADD BLOCKED: ");
            writeToLog(g_string_buf);
           
            getExPath(g_string_buf);
            insert_item_uniq(l, g_string_buf);
            break;
            
        case del:
            strncpy(g_string_buf, (char*)data, min(stringSize, len));
            printf("Kext delete MK for path: %s\n", g_string_buf);
            writeToLog("DELETE BLOCKED: ");
            writeToLog(g_string_buf);
            
            getExPath(g_string_buf);
            delete_item(l,g_string_buf);
            break;
            
        default:
            ret = ENOTSUP;
            break;
    }
    return ret;
}

/* Listen/ Read MK/ Write MK  PART */

static int callbackProcessListener
(
 kauth_cred_t    credential,
 void *          idata,
 kauth_action_t  action,
 uintptr_t       arg0,
 uintptr_t       arg1,
 uintptr_t       arg2,
 uintptr_t       arg3
 )
{
    char path[buffSize]= {0};
    uint8_t mKey[mkSize]={0};
    
    int size = buffSize;
    if(KAUTH_VNODE_EXECUTE == action){
        vn_getpath((vnode_t) arg1,path,&size);
         
        if(vnode_vtype((vnode_t)arg1)==VREG){
    
            checkMagicKey(path, (vnode_t)arg1, (vfs_context_t)arg0, mKey);
         
            if(find_item(l, mKey))
            {
                writeToLog("PERMISSION DENIED");
                writeToLog(path);
                return  KAUTH_RESULT_DENY;
            }
        }

    }
    
    return KAUTH_RESULT_DEFER;
}

void checkMagicKey(char *path,vnode_t vn,vfs_context_t vfs,uint8_t magicKey[]){
   
    char adr[readBuff]= {0};
    struct uuid_command *uid = NULL;
    
    if(strlen(path)>2){
        
        if (vnode_open(path, O_RDONLY, (0), 0, &vn, vfs) == 0) {
            uio_t read;
            read = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
            uio_addiov(read, (user_addr_t)adr, readBuff);
            
            VNOP_READ(vn, read,  0, vfs);
                      
            uid = (struct uuid_command*)adr;
            for(int i =0; i<mkSize;i++){
                magicKey[i]=uid->uuid[i];
            }
        
            uio_free(read);
            vnode_close(vn, 0, vfs);
        } else {
          
        }
    }
}

/* LOG PART */

int getLogLength()
{
    int result = 0;
    while(result == 0)
    {
        result = moveOffset();
    }
    return result;
}

int moveOffset()
{
    vnode_t vn;
    vfs_context_t vfs = vfs_context_current();
    int result = vnode_open("/var/log/firewall.log", FREAD, (0), 0, &vn, vfs);
    if (result == 0)
    {
        char buf[1];
        uio_t read;
        read = uio_create(1, offset, UIO_SYSSPACE, UIO_READ);
        uio_addiov(read, (user_addr_t)buf, 1);
        int resultRead = VNOP_READ(vn, read, 0, vfs);
        uio_free(read);
        vnode_close(vn, 0, vfs);
        if(resultRead == 0 && buf[0] != '\0' && (buf[0] != '\n' || buf[0] != previousChar))
        {
            offset++;
            previousChar = buf[0];
            return 0;
        }
        printf("Reading log stopped at %d\n", offset);
        return 2;
    }
    printf("Can't open log\n");
    return 1;
}

struct dateTime dateAndTimeFromSeconds()
{
    struct dateTime dt;
    uint32_t timeSec;
    uint32_t timeMsec;
    clock_get_calendar_microtime(&timeSec, &timeMsec);
    uint32_t time = timeSec;
    uint32_t seconds = time % 60;
    time /= 60;
    uint32_t minutes = time % 60;
    time /= 60;
    uint32_t hours = time % 24 + 3;
    time /= 24;
    uint32_t months = 0;
    int days = 1;
    uint32_t years = time / 365;
    days += time % 365 - (int)((years + 2) / 4);
    int isCurrentYearLeap = 0;
    if(days < 0)
    {
        days += 365;
        if((years - 2) % 4 == 0)
        {
            isCurrentYearLeap = 1;
            days++;
        }
        years--;
    }
    years += 1970;
    int daysInMonths[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    if(isCurrentYearLeap)
    {
        daysInMonths[1] = 29;
    }
    months = 1;
    for(int i = 0; ;i++)
    {
        if(days>daysInMonths[i])
        {
            days -= daysInMonths[i];
            months++;
        }
        else
        {
            break;
        }
    }
    dt.year = years;
    dt.month = months;
    dt.day = days;
    dt.hour = hours;
    dt.minute = minutes;
    dt.second = seconds;
    return dt;
}

void writeToLog(char text[])
{
    struct dateTime dt = dateAndTimeFromSeconds();
    int len = 24 + strlen(text);
    int lenSmall = 3;
    int lenBig = 5;
    char result[len];
    char day[lenSmall];
    if(dt.day>9)
    {
        snprintf(day, lenSmall, "%d", dt.day);
    }
    else
    {
        snprintf(day, lenSmall, "0%d", dt.day);
    }
    char month[lenSmall];
    if(dt.month>9)
    {
        snprintf(month, lenSmall, "%d", dt.month);
    }
    else
    {
        snprintf(month, lenSmall, "0%d", dt.month);
    }
    char year[lenBig];
    snprintf(year, lenBig, "%d", dt.year);
    char hour[lenSmall];
    if(dt.hour>9)
    {
        snprintf(hour, lenSmall, "%d", dt.hour);
    }
    else
    {
        snprintf(hour, lenSmall, "0%d", dt.hour);
    }
    char minute[lenSmall];
    if(dt.minute>9)
    {
        snprintf(minute, lenSmall, "%d", dt.minute);
    }
    else
    {
        snprintf(minute, lenSmall, "0%d", dt.minute);
    }
    char second[lenSmall];
    if(dt.second>9)
    {
        snprintf(second, lenSmall, "%d", dt.second);
    }
    else
    {
        snprintf(second, lenSmall, "0%d", dt.second);
    }
    snprintf(result, len, "[%s.%s.%s %s:%s:%s] %s\n", day, month, year, hour, minute, second, text);
    write(result);
}

void write(char text[])
{
    vnode_t vn;
    vfs_context_t vfs = logvfs;
    int result = 1;
    if(isWriteToLogFirst == 1)
    {
        result = vnode_open("/var/log/firewall.log",( FWRITE | O_CREAT | O_TRUNC ), (0), 0, &vn, vfs);
    }
    else
    {
        result = vnode_open("/var/log/firewall.log",( FWRITE | O_CREAT | O_APPEND ), (0), 0, &vn, vfs);
    }
    if (result == 0)
    {
        uio_t write;
        write = uio_create(1, offset, UIO_SYSSPACE, UIO_WRITE);
        uio_addiov(write, (user_addr_t)text, strlen(text));
        VNOP_WRITE(vn, write, 0, vfs);
        uio_free(write);
        vnode_close(vn, 0, vfs);
        offset += strlen(text);
        isWriteToLogFirst = 0;
    }
    else
    {
        printf("Can't write to log\n");
    }
}

